﻿using System;
using System.Net;
using caedisi.Web.DataModel;
using System.Linq;
using caedisi.Models;
using caedisi.Common;
using System.ServiceModel.DomainServices.Client;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.IO;
using System.Reflection;
using System.Windows;


namespace caedisi.Helpers
{
    /// <summary>
    /// An helper of Lov entities
    /// </summary>
    public class EntityHelper
    {
        /// <summary>
        /// Gets the type of the cell state.
        /// </summary>
        /// <param name="cellStateTypeId">The cell state type id.</param>
        /// <returns></returns>
        public static CellStateType GetCellStateType(int cellStateTypeId)
        {
            if (Globals.Instance.Lovs.CellStateTypes == null)
                return null;

            IEnumerable<CellStateType> list = Globals.Instance.Lovs.CellStateTypes.Where(cellStateType => cellStateType.CellStateTypeId == cellStateTypeId);

            if (list != null && list.Count() > 0) return list.First();

            return null;
        }


        /// <summary>
        /// Gets the type of the neighbourhood.
        /// </summary>
        /// <param name="neighbourhoodTypeId">The neighbourhood type id.</param>
        /// <returns></returns>
        public static NeighbourhoodType GetNeighbourhoodType(int neighbourhoodTypeId)
        {
            if (Globals.Instance.Lovs.CellStateTypes == null)
                return null;

            NeighbourhoodType result = Globals.Instance.Lovs.NeighbourhoodTypes.Where(neighbourhoodType => neighbourhoodType.NeighbourhoodTypeId == neighbourhoodTypeId).First();

            return result;
        }

        /// <summary>
        /// Generates the generic transition rule.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="description">The description.</param>
        /// <param name="neighbourhoodType">Type of the neighbourhood.</param>
        /// <param name="radiusVisibility">The radius visibility.</param>
        /// <returns></returns>
        public static TransitionRule GenerateGenericTransitionRule(string name, string description, NeighbourhoodType neighbourhoodType, int radiusVisibility)
        {
            TransitionRule transitionRule = new TransitionRule()
            {
                Name = name,
                Description = description
            };

            int minOffset = radiusVisibility * (-1);
            int maxOffset = radiusVisibility * (1);

            int xIndexCenterCell = maxOffset;
            int yIndexCenterCell = maxOffset;
            int zIndexCenterCell = 0;  //for now we only deal with 2D

            //1. Generate recursively the neighbouring cells data according to the neighbourhoodtype
            int currentRadiusIndex = 0;
            EntityHelper.GenerateListNeighboursData(transitionRule, xIndexCenterCell, yIndexCenterCell, zIndexCenterCell, neighbourhoodType, currentRadiusIndex, radiusVisibility);

            return transitionRule;
        }

        /// <summary>
        /// Generates the generic transition rule.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="description">The description.</param>
        /// <param name="neighbourhoodTypeId">The neighbourhood type id.</param>
        /// <param name="radiusVisibility">The radius visibility.</param>
        /// <returns></returns>
        public static TransitionRule GenerateGenericTransitionRule(string name, string description, int neighbourhoodTypeId, int radiusVisibility)
        {
            NeighbourhoodType neighbourhoodType = EntityHelper.GetNeighbourhoodType(neighbourhoodTypeId);
            return GenerateGenericTransitionRule(name, description, neighbourhoodType, radiusVisibility);
        }

        /// <summary>
        /// Generates the list neighbours data.  
        /// The code recursively generate the list of neighbours starting from the center cell based on the neighbourhood type and the radius visibility.
        /// The approach used a breadth-first-search (BFS)
        /// </summary>
        /// <param name="transitionRule">The transition rule.</param>
        /// <param name="xIndexCenterCell">The x index center cell.</param>
        /// <param name="yIndexCenterCell">The y index center cell.</param>
        /// <param name="zIndexCenterCell">The z index center cell.</param>
        /// <param name="neighbourhoodType">Type of the neighbourhood.</param>
        /// <param name="currentRadiusIndex">Index of the current radius.</param>
        /// <param name="radiusVisibility">The radius visibility.</param>
        /// <returns></returns>
        public static EntityCollection<NeighbourhoodStateConfiguration> GenerateListNeighboursData(TransitionRule transitionRule, int xIndexCenterCell, int yIndexCenterCell, int zIndexCenterCell, NeighbourhoodType neighbourhoodType, int currentRadiusIndex, int radiusVisibility)
        {
            currentRadiusIndex++;
            if (currentRadiusIndex > radiusVisibility)   //the recursion ends when the distance thresold  has ben met
                return null;

            //we use this storage to store the list of existing neighbourhood coordinate
            Collection<Tuple<int, int, int>> listOfExistingNeighboursCoordinate = new Collection<Tuple<int, int, int>>();

            if (neighbourhoodType.IsTopLeftON)
            {
                int xIndex = xIndexCenterCell - 1;
                int yIndex = yIndexCenterCell - 1;
                int zIndex = 0;

                GenerateDirectNeighbourData(transitionRule, xIndex, yIndex, zIndex, currentRadiusIndex, radiusVisibility);
                listOfExistingNeighboursCoordinate.Add(new Tuple<int, int, int>(xIndex, yIndex, zIndex));
            }

            if (neighbourhoodType.IsTopON)
            {
                int xIndex = xIndexCenterCell - 1;
                int yIndex = yIndexCenterCell;
                int zIndex = 0;

                GenerateDirectNeighbourData(transitionRule, xIndex, yIndex, zIndex, currentRadiusIndex, radiusVisibility);
                listOfExistingNeighboursCoordinate.Add(new Tuple<int, int, int>(xIndex, yIndex, zIndex));
            }

            if (neighbourhoodType.IsTopRightON)
            {
                int xIndex = xIndexCenterCell - 1;
                int yIndex = yIndexCenterCell + 1;
                int zIndex = 0;

                GenerateDirectNeighbourData(transitionRule, xIndex, yIndex, zIndex, currentRadiusIndex, radiusVisibility);
                listOfExistingNeighboursCoordinate.Add(new Tuple<int, int, int>(xIndex, yIndex, zIndex));
            }

            if (neighbourhoodType.IsRightON)
            {
                int xIndex = xIndexCenterCell;
                int yIndex = yIndexCenterCell + 1;
                int zIndex = 0;

                GenerateDirectNeighbourData(transitionRule, xIndex, yIndex, zIndex, currentRadiusIndex, radiusVisibility);
                listOfExistingNeighboursCoordinate.Add(new Tuple<int, int, int>(xIndex, yIndex, zIndex));
            }

            if (neighbourhoodType.IsBottomRightON)
            {
                int xIndex = xIndexCenterCell + 1;
                int yIndex = yIndexCenterCell + 1;
                int zIndex = 0;

                GenerateDirectNeighbourData(transitionRule, xIndex, yIndex, zIndex, currentRadiusIndex, radiusVisibility);
                listOfExistingNeighboursCoordinate.Add(new Tuple<int, int, int>(xIndex, yIndex, zIndex));
            }

            if (neighbourhoodType.IsBottomON)
            {
                int xIndex = xIndexCenterCell + 1;
                int yIndex = yIndexCenterCell;
                int zIndex = 0;

                GenerateDirectNeighbourData(transitionRule, xIndex, yIndex, zIndex, currentRadiusIndex, radiusVisibility);
                listOfExistingNeighboursCoordinate.Add(new Tuple<int, int, int>(xIndex, yIndex, zIndex));
            }

            if (neighbourhoodType.IsBottomLeftON)
            {
                int xIndex = xIndexCenterCell + 1;
                int yIndex = yIndexCenterCell - 1;
                int zIndex = 0;

                GenerateDirectNeighbourData(transitionRule, xIndex, yIndex, zIndex, currentRadiusIndex, radiusVisibility);
                listOfExistingNeighboursCoordinate.Add(new Tuple<int, int, int>(xIndex, yIndex, zIndex));
            }

            if (neighbourhoodType.IsLeftON)
            {
                int xIndex = xIndexCenterCell;
                int yIndex = yIndexCenterCell - 1;
                int zIndex = 0;

                GenerateDirectNeighbourData(transitionRule, xIndex, yIndex, zIndex, currentRadiusIndex, radiusVisibility);
                listOfExistingNeighboursCoordinate.Add(new Tuple<int, int, int>(xIndex, yIndex, zIndex));
            }


            //Now need to call recursively the generation for each existing neighbour
            foreach (Tuple<int, int, int> coordinate in listOfExistingNeighboursCoordinate)
                GenerateListNeighboursData(transitionRule, coordinate.Item1, coordinate.Item2, coordinate.Item3, neighbourhoodType, currentRadiusIndex, radiusVisibility);


            return transitionRule.NeighbourhoodStateConfigurations;
        }

        /// <summary>
        /// Generates the direct neighbour data.
        /// </summary>
        /// <param name="transitionRule">The transition rule.</param>
        /// <param name="xIndex">Index of the x.</param>
        /// <param name="yIndex">Index of the y.</param>
        /// <param name="zIndex">Index of the z.</param>
        public static void GenerateDirectNeighbourData(TransitionRule transitionRule, int xIndex, int yIndex, int zIndex, int currentRadiusIndex, int radiusVisibility)
        {
            int x_offset = xIndex - radiusVisibility;
            int y_offset = yIndex - radiusVisibility;
            int z_offset = 0;

            if (x_offset == 0 && y_offset == 0 && z_offset == 0) // no need to create the center data
                return;


            bool neighbourNotCreatedYet = transitionRule.NeighbourhoodStateConfigurations.Where(
                neighbourhoodStateConfiguration => neighbourhoodStateConfiguration.x_offset == x_offset
                                            && neighbourhoodStateConfiguration.y_offset == y_offset
                                            && neighbourhoodStateConfiguration.z_offset == z_offset).Count() == 0;
            if (neighbourNotCreatedYet)
            {
                NeighbourhoodStateConfiguration newNeighbourState = new NeighbourhoodStateConfiguration()
                {
                    x_offset = x_offset,
                    y_offset = y_offset,
                    z_offset = z_offset,
                    CellStateTypeId = (int)EnumCellStateType.Generic,
                    DistanceFromCenter = currentRadiusIndex
                };
                transitionRule.NeighbourhoodStateConfigurations.Add(newNeighbourState);
            }
        }


        public static void SetCellularAutomataHierarchy(CellularAutomata cellularAutomata, EnumCellStateType defaultCellStateType)
        {
            int numberOfRows = cellularAutomata.Height;
            int numberOfColumns = cellularAutomata.Width;
            NeighbourhoodType neighbourhoodType = EntityHelper.GetNeighbourhoodType(cellularAutomata.NeighbourhoodTypeId);

            cellularAutomata.CellularAutomataAsArray = new Cell[numberOfRows, numberOfColumns, 1];

            //creating the cells
            for (int i = 0; i < numberOfRows; i++)
            {
                for (int j = 0; j < numberOfColumns; j++)
                {
                    int k = 0; //for now we only deal with 2D
                    Cell newCellularAutomataCell = new Cell()
                    {
                        CellId = Guid.NewGuid(),
                        CellStateTypeId = (int)defaultCellStateType, //clean
                        x = i,
                        y = j,
                        z = k,
                        Label = "(" + i + "," + j + "," + k + ")",
                        Description = "cell " + "(" + i + "," + j + "," + k + ")" + " of " + cellularAutomata.Name
                    };
                    cellularAutomata.Cells.Add(newCellularAutomataCell);

                    cellularAutomata.CellularAutomataAsArray[i, j, 0] = newCellularAutomataCell;
                }
            }

            //arranging the grid hierarchy based on neighbourhood type
            for (int i = 0; i < numberOfRows; i++)
            {
                for (int j = 0; j < numberOfColumns; j++)
                {
                    int k = 0;

                    if (neighbourhoodType.IsTopLeftON)
                    {
                        int xIndex = i - 1;
                        int yIndex = j - 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical && cellularAutomata.CircularHorizontal)
                                  || (xIndex >= 0 && yIndex >= 0)
                                  || (cellularAutomata.CircularHorizontal && xIndex >= 0)
                                  || (cellularAutomata.CircularVertical && yIndex >= 0);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].TopLeftCellId = cellularAutomata.CellularAutomataAsArray[MathMod(xIndex, numberOfRows), MathMod(yIndex, numberOfColumns), zIndex].CellId;

                    }

                    if (neighbourhoodType.IsTopON)
                    {
                        int xIndex = i - 1;
                        int yIndex = j;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical || xIndex >= 0);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].TopCellId = cellularAutomata.CellularAutomataAsArray[MathMod(xIndex, numberOfRows), yIndex, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsTopRightON)
                    {
                        int xIndex = i - 1;
                        int yIndex = j + 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical && cellularAutomata.CircularHorizontal)
                               || (xIndex >= 0 && yIndex < numberOfColumns)
                               || (cellularAutomata.CircularHorizontal && xIndex >= 0)
                               || (cellularAutomata.CircularVertical && yIndex < numberOfColumns);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].TopRightCellId = cellularAutomata.CellularAutomataAsArray[MathMod(xIndex, numberOfRows), yIndex % numberOfColumns, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsRightON)
                    {
                        int xIndex = i;
                        int yIndex = j + 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularHorizontal || yIndex < numberOfColumns);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].RightCellId = cellularAutomata.CellularAutomataAsArray[xIndex, yIndex % numberOfColumns, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsBottomRightON)
                    {
                        int xIndex = i + 1;
                        int yIndex = j + 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical && cellularAutomata.CircularHorizontal)
                               || (xIndex < numberOfRows && yIndex < numberOfColumns)
                               || (cellularAutomata.CircularHorizontal && xIndex < numberOfRows)
                               || (cellularAutomata.CircularVertical && yIndex < numberOfColumns);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].BottomRightCellId = cellularAutomata.CellularAutomataAsArray[xIndex % numberOfRows, yIndex % numberOfColumns, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsBottomON)
                    {
                        int xIndex = i + 1;
                        int yIndex = j;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical || xIndex < numberOfRows);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].BottomCellId = cellularAutomata.CellularAutomataAsArray[xIndex % numberOfRows, yIndex, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsBottomLeftON)
                    {
                        int xIndex = i + 1;
                        int yIndex = j - 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical && cellularAutomata.CircularHorizontal)
                              || (xIndex < numberOfRows && yIndex >= 0)
                              || (cellularAutomata.CircularHorizontal && xIndex < numberOfRows)
                              || (cellularAutomata.CircularVertical && yIndex >= 0);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].BottomLeftCellId = cellularAutomata.CellularAutomataAsArray[xIndex % numberOfRows, MathMod(yIndex, numberOfColumns), zIndex].CellId;

                    }

                    if (neighbourhoodType.IsLeftON)
                    {
                        int xIndex = i;
                        int yIndex = j - 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularHorizontal || yIndex >= 0);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].LeftCellId = cellularAutomata.CellularAutomataAsArray[xIndex, MathMod(yIndex, numberOfColumns), zIndex].CellId;
                    }
                }
            }
        }

        public static void SetCellularAutomataHierarchyForPartition(CellularAutomata cellularAutomata, int[, ,] list)
        {
            int numberOfRows = cellularAutomata.Height;
            int numberOfColumns = cellularAutomata.Width;
            NeighbourhoodType neighbourhoodType = EntityHelper.GetNeighbourhoodType(cellularAutomata.NeighbourhoodTypeId);

            cellularAutomata.CellularAutomataAsArray = new Cell[numberOfRows, numberOfColumns, 1];

            //creating the cells
            for (int i = 0; i < numberOfRows; i++)
            {
                for (int j = 0; j < numberOfColumns; j++)
                {
                    int a = list[i, j, 0];
                    int k = 0; //for now we only deal with 2D
                    Cell newCellularAutomataCell = new Cell()
                    {
                        CellId = Guid.NewGuid(),
                        CellStateTypeId = list[i, j, k], //clean
                        x = i,
                        y = j,
                        z = k,
                        Label = "(" + i + "," + j + "," + k + ")",
                        Description = "cell " + "(" + i + "," + j + "," + k + ")" + " of " + cellularAutomata.Name
                    };
                    cellularAutomata.Cells.Add(newCellularAutomataCell);

                    cellularAutomata.CellularAutomataAsArray[i, j, 0] = newCellularAutomataCell;
                }
            }

            //arranging the grid hierarchy based on neighbourhood type
            for (int i = 0; i < numberOfRows; i++)
            {
                for (int j = 0; j < numberOfColumns; j++)
                {
                    int k = 0;

                    if (neighbourhoodType.IsTopLeftON)
                    {
                        int xIndex = i - 1;
                        int yIndex = j - 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical && cellularAutomata.CircularHorizontal)
                                  || (xIndex >= 0 && yIndex >= 0)
                                  || (cellularAutomata.CircularHorizontal && xIndex >= 0)
                                  || (cellularAutomata.CircularVertical && yIndex >= 0);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].TopLeftCellId = cellularAutomata.CellularAutomataAsArray[MathMod(xIndex, numberOfRows), MathMod(yIndex, numberOfColumns), zIndex].CellId;

                    }

                    if (neighbourhoodType.IsTopON)
                    {
                        int xIndex = i - 1;
                        int yIndex = j;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical || xIndex >= 0);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].TopCellId = cellularAutomata.CellularAutomataAsArray[MathMod(xIndex, numberOfRows), yIndex, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsTopRightON)
                    {
                        int xIndex = i - 1;
                        int yIndex = j + 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical && cellularAutomata.CircularHorizontal)
                               || (xIndex >= 0 && yIndex < numberOfColumns)
                               || (cellularAutomata.CircularHorizontal && xIndex >= 0)
                               || (cellularAutomata.CircularVertical && yIndex < numberOfColumns);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].TopRightCellId = cellularAutomata.CellularAutomataAsArray[MathMod(xIndex, numberOfRows), yIndex % numberOfColumns, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsRightON)
                    {
                        int xIndex = i;
                        int yIndex = j + 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularHorizontal || yIndex < numberOfColumns);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].RightCellId = cellularAutomata.CellularAutomataAsArray[xIndex, yIndex % numberOfColumns, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsBottomRightON)
                    {
                        int xIndex = i + 1;
                        int yIndex = j + 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical && cellularAutomata.CircularHorizontal)
                               || (xIndex < numberOfRows && yIndex < numberOfColumns)
                               || (cellularAutomata.CircularHorizontal && xIndex < numberOfRows)
                               || (cellularAutomata.CircularVertical && yIndex < numberOfColumns);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].BottomRightCellId = cellularAutomata.CellularAutomataAsArray[xIndex % numberOfRows, yIndex % numberOfColumns, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsBottomON)
                    {
                        int xIndex = i + 1;
                        int yIndex = j;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical || xIndex < numberOfRows);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].BottomCellId = cellularAutomata.CellularAutomataAsArray[xIndex % numberOfRows, yIndex, zIndex].CellId;
                    }

                    if (neighbourhoodType.IsBottomLeftON)
                    {
                        int xIndex = i + 1;
                        int yIndex = j - 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularVertical && cellularAutomata.CircularHorizontal)
                              || (xIndex < numberOfRows && yIndex >= 0)
                              || (cellularAutomata.CircularHorizontal && xIndex < numberOfRows)
                              || (cellularAutomata.CircularVertical && yIndex >= 0);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].BottomLeftCellId = cellularAutomata.CellularAutomataAsArray[xIndex % numberOfRows, MathMod(yIndex, numberOfColumns), zIndex].CellId;

                    }

                    if (neighbourhoodType.IsLeftON)
                    {
                        int xIndex = i;
                        int yIndex = j - 1;
                        int zIndex = k;

                        bool exist = (cellularAutomata.CircularHorizontal || yIndex >= 0);
                        if (exist)
                            cellularAutomata.CellularAutomataAsArray[i, j, k].LeftCellId = cellularAutomata.CellularAutomataAsArray[xIndex, MathMod(yIndex, numberOfColumns), zIndex].CellId;
                    }
                }
            }
        }


        /// <summary>
        /// Mathes the mod. The native % in C# does not give a positive number 
        /// </summary>
        /// <param name="dividend">The dividend.</param>
        /// <param name="divisor">The divisor.</param>
        /// <returns></returns>
        public static int MathMod(int dividend, int divisor)
        {
            return (Math.Abs(dividend * divisor) + dividend) % divisor;
        }

        /// <summary>
        /// Validates the transition rules collision.
        /// </summary>
        /// <param name="cellularAutomata">The cellular automata.</param>
        /// <returns></returns>
        public static Collection<RuleValidationError> ValidateTransitionRulesCollision(CellularAutomata cellularAutomata)
        {
            Collection<RuleValidationError> result = new Collection<RuleValidationError>();

            int size = cellularAutomata.TransitionRules.Count;

            for (int i = 0; i < size; i++)
            {
                TransitionRule t1 = cellularAutomata.TransitionRules.ElementAt(i);

                for (int j = i + 1; j < size; j++)
                {
                    TransitionRule t2 = cellularAutomata.TransitionRules.ElementAt(j);
                    // A collision occurs between 2 rules when their current state is the same, their resulting state are different 
                    // however the input neighbourhood configuration are similar
                    if (t1.CurrentCellStateTypeId == t2.CurrentCellStateTypeId && t1.ResultCellStateTypeId != t2.ResultCellStateTypeId)
                    {
                        //validate if the input are the same.
                        bool isInputIsomorphic = IsInputIsomorphic(t1, t2);
                        if (isInputIsomorphic)
                            result.Add(new RuleValidationError(t1, t2));
                    }
                }
            }

            return result;

        }


        /// <summary>
        /// Validates the transition rules inclusion.
        /// </summary>
        /// <param name="cellularAutomata">The cellular automata.</param>
        /// <returns></returns>
        public static Collection<RuleValidationError> ValidateTransitionRulesInclusion(CellularAutomata cellularAutomata)
        {
            Collection<RuleValidationError> result = new Collection<RuleValidationError>();

            int size = cellularAutomata.TransitionRules.Count;

            for (int i = 0; i < size; i++)
            {
                TransitionRule t1 = cellularAutomata.TransitionRules.ElementAt(i);

                for (int j = i + 1; j < size; j++)
                {
                    TransitionRule t2 = cellularAutomata.TransitionRules.ElementAt(j);
                    // An inclusion occurs between 2 rules if  their current state are the same , their resulting state is the same, 
                    // and the neighbourhood configuration neighbourhood configuration are similar or one is contained in another
                    if (//!t1.IsSystemGenerated && !t2.IsSystemGenerated &&
                        t1 != t2 &&
                        t1.CurrentCellStateTypeId == t2.CurrentCellStateTypeId &&
                        t1.ResultCellStateTypeId == t2.ResultCellStateTypeId)
                    {
                        //validate if the input are the same.
                        bool isInputIsomorphic = IsInputIsomorphic(t1, t2);
                        if (isInputIsomorphic)
                            result.Add(new RuleValidationError(t1, t2));
                    }
                }
            }

            return result;
        }

        ////Test Intersection
        //public static Collection<String> ValidateIntersection(int[,] matriceTest, short height, short width)
        //{
        //    Collection<String> result = new Collection<string>();

        //    for (int i = 0; i < height; i++)
        //    {
        //        for (int j = 0; j < width; j++)
        //        {
        //            // (1 : ok
        //            //  2 : intersection "error")
        //            if (matriceTest[i, j] == 2)
        //            {
        //                result.Add("Intersection");
        //                j = width;
        //                i = height;
        //            }
        //        }
        //    }
        //    return result;
        //}

        ////Test Not Used Cell
        //public static Collection<String> ValidateEmpty(int[,] matriceTest, short height, short width)
        //{
        //    Collection<String> result = new Collection<string>();
        //    for (int i = 0; i < height; i++)
        //    {
        //        for (int j = 0; j < width; j++)
        //        {
        //            // (0 : cell not used "error"
        //            //  1 : ok)

        //            if (matriceTest[i, j] == 0)
        //            {
        //                result.Add("NotUsed");
        //                j = width;
        //                i = height;
        //            }
        //        }
        //    }
        //    return result;
        //}


        public static bool IsInputIsomorphic(TransitionRule t1, TransitionRule t2)
        {
            bool isInputIsomorphic = true;

            foreach (NeighbourhoodStateConfiguration t1Neighbour in t1.NeighbourhoodStateConfigurations)
            {
                if (t1Neighbour.CellStateTypeId != (int)EnumCellStateType.Generic) //no need to check if one is generic
                {
                    NeighbourhoodStateConfiguration t2EquivalentNeighbour = t2.NeighbourhoodStateConfigurations.Where(neighbour => neighbour.x_offset == t1Neighbour.x_offset
                                                                                                                              && neighbour.y_offset == t1Neighbour.y_offset
                                                                                                                              && neighbour.z_offset == t1Neighbour.z_offset).SingleOrDefault();
                    if (t2EquivalentNeighbour != null
                        && t2EquivalentNeighbour.CellStateTypeId != (int)EnumCellStateType.Generic
                        && t1Neighbour.CellStateTypeId != t2EquivalentNeighbour.CellStateTypeId)
                    {
                        isInputIsomorphic = false;
                        break;
                    }
                }
            }
            return isInputIsomorphic;
        }

        /// <summary>
        /// Gets the neighbour cell state type id referenced by the ruleStateConfiguration
        /// </summary>
        /// <param name="myCell">My cell.</param>
        /// <param name="cellularAutomataAsArray">The cellular automata as array.</param>
        /// <param name="ruleStateConfiguration">The rule state configuration.</param>
        /// <param name="isCircularVertical">if set to <c>true</c> [is circular vertical].</param>
        /// <param name="IsCircularHorizontal">if set to <c>true</c> [is circular horizontal].</param>
        /// <param name="outOfBoundCellStateTypeId">The out of bound cell state type id.</param>
        /// <returns></returns>
        public static int GetNeighbourCellStateTypeId(Cell myCell, Cell[, ,] cellularAutomataAsArray, NeighbourhoodStateConfiguration ruleStateConfiguration, bool isCircularVertical, bool IsCircularHorizontal, int outOfBoundCellStateTypeId)
        {
            int numberOfRows = cellularAutomataAsArray.GetUpperBound(0) + 1;
            int numberOfColumns = cellularAutomataAsArray.GetUpperBound(1) + 1;


            int xIndexNeighbour = myCell.x + ruleStateConfiguration.x_offset;
            int yIndexNeighbour = myCell.y + ruleStateConfiguration.y_offset;
            int zIndexNeighbour = myCell.z + ruleStateConfiguration.z_offset;

            //find the cell pointed by these coordinates. TODO make it complete
            int neighbourCellStateTypeId;

            bool doesXIndexExist = isCircularVertical || (xIndexNeighbour >= 0 && xIndexNeighbour < numberOfRows);
            bool doesYIndexExist = IsCircularHorizontal || (yIndexNeighbour >= 0 && yIndexNeighbour < numberOfColumns);
            bool doesZIndexExist = true;

            if (doesXIndexExist && doesYIndexExist && doesZIndexExist)
            {
                neighbourCellStateTypeId = cellularAutomataAsArray[
                    EntityHelper.MathMod(xIndexNeighbour, numberOfRows),
                    EntityHelper.MathMod(yIndexNeighbour, numberOfColumns),
                    zIndexNeighbour].CellStateTypeId;
            }
            else
            {
                neighbourCellStateTypeId = outOfBoundCellStateTypeId;
            }

            return neighbourCellStateTypeId;
        }

        public static int GetNeighbourCellStateTypeId(Cell myCell, InvertedCellItem[, ,] previousCellularAutomataAsArrayInvertedCellItem, NeighbourhoodStateConfiguration ruleStateConfiguration, bool isCircularVertical, bool IsCircularHorizontal, int outOfBoundCellStateTypeId)
        {
            Cell[, ,] cells = Convert(previousCellularAutomataAsArrayInvertedCellItem);

            return GetNeighbourCellStateTypeId(myCell, cells, ruleStateConfiguration, isCircularVertical, IsCircularHorizontal, outOfBoundCellStateTypeId);
        }

        public static Cell[, ,] Convert(InvertedCellItem[, ,] invertedCells)
        {
            int numberOfRows = invertedCells.GetUpperBound(0) + 1;
            int numberOfColumns = invertedCells.GetUpperBound(1) + 1;
            int numberOfZ = invertedCells.GetUpperBound(2) + 1;

            Cell[, ,] cells = new Cell[numberOfRows, numberOfColumns, numberOfZ];

            for (int i = 0; i < numberOfRows; i++)
            {
                for (int j = 0; j < numberOfColumns; j++)
                {
                    for (int k = 0; k < numberOfZ; k++)
                    {
                        cells[i, j, k] = new Cell()
                        {
                            CellId = invertedCells[i, j, k].CellId,
                            CellStateTypeId = invertedCells[i, j, k].CellStateTypeId
                        };
                    }
                }
            }

            return cells;
        }

        public static int GetCardinalitySpecificState(Simulation simulation, int step, EnumCellStateType enumCellStateType)
        {
            int result = 0;

            if (simulation == null)
                return result;

            int cellStateTypeId = (int)enumCellStateType;

            result = simulation.SystemStateDictionary.Values.Count(
                            item => item.Step == step && item.CellStateTypeId == cellStateTypeId);

            return result;

        }

        /// <summary>
        /// Get the progression with percentage
        /// </summary>
        /// <param name="simulation"></param>
        /// <param name="step"></param>
        /// <param name="enumCellStateType"></param>
        /// <param name="total"></param>
        /// <returns></returns>
        public static double GetCardinalitySpecificStatePercentage(Simulation simulation, int step, EnumCellStateType enumCellStateType, float total)
        {
            double result = 0;
            
            if (simulation == null)
                return result;

            int cellStateTypeId = (int)enumCellStateType;

            result = simulation.SystemStateDictionary.Values.Count(
                            item => item.Step == step && item.CellStateTypeId == cellStateTypeId);
            
            return (result / total) * 100;

        }

        /// <summary>
        /// Get the height of the bar chart
        /// </summary>
        /// <param name="simulation"></param>
        /// <param name="step"></param>
        /// <param name="enumCellStateType"></param>
        /// <param name="total"></param>
        /// <returns></returns>
        public static double GetCardinalitySpecificStateZoom(Simulation simulation, int step, EnumCellStateType enumCellStateType, int total)
        {
            double result = 0;

            if (simulation == null)
                return result;

            int cellStateTypeId = (int)enumCellStateType;

            result = simulation.SystemStateDictionary.Values.Count(
                            item => item.Step == step && item.CellStateTypeId == cellStateTypeId);
            
            return (result / total) * 271; ;
            
        }

        public static void GenerateContaminationRule(CellularAutomata cellularAutomata, NeighbourhoodType selectedNeighbourhoodType, ref int numberOfRulesCreated)
        {
            //using a temporary rule to loop through all existing neighbours
            TransitionRule tempRule = EntityHelper.GenerateGenericTransitionRule(
                    string.Empty,
                    string.Empty,
                    selectedNeighbourhoodType,
                    cellularAutomata.RadiusVisibility);

            //then create all contamination rule
            foreach (NeighbourhoodStateConfiguration configuration in tempRule.NeighbourhoodStateConfigurations)
            {
                //only generate when the contamination radius is smaller than the visibility radius
                if (configuration.DistanceFromCenter <= cellularAutomata.RadiusContamination)
                {
                    numberOfRulesCreated++;

                    TransitionRule newContaminationRule = EntityHelper.GenerateGenericTransitionRule(
                        "Rule #" + numberOfRulesCreated + " (Contamination)",
                        "Contamination rule infecting a clean cell when a contaminated cell is located at a contamination radius=" + cellularAutomata.RadiusContamination + " and a visibility radius=" + cellularAutomata.RadiusVisibility,
                        selectedNeighbourhoodType as NeighbourhoodType,
                        cellularAutomata.RadiusVisibility);

                    newContaminationRule.CurrentCellStateTypeId = (int)EnumCellStateType.Decontaminated;
                    newContaminationRule.ResultCellStateTypeId = (int)EnumCellStateType.Contaminated;
                    newContaminationRule.IsSystemGenerated = true;

                    NeighbourhoodStateConfiguration thisConfiguration = newContaminationRule.NeighbourhoodStateConfigurations.Where(stateConfiguration => stateConfiguration.x_offset == configuration.x_offset
                                                                                && stateConfiguration.y_offset == configuration.y_offset
                                                                                && stateConfiguration.z_offset == configuration.z_offset).First();

                    //set this neighourhood to contaminated and leave the remaining neighbours as generic
                    thisConfiguration.CellStateTypeId = (int)EnumCellStateType.Contaminated;

                    cellularAutomata.TransitionRules.Add(newContaminationRule);
                }
            }
        }

        public static void GenerateDecontaminatedRule(CellularAutomata cellularAutomata, NeighbourhoodType selectedNeighbourhoodType, ref int numberOfRulesCreated)
        {
            numberOfRulesCreated++;

            TransitionRule newDecontaminatedRule = EntityHelper.GenerateGenericTransitionRule(
                          "Rule #" + numberOfRulesCreated + " (Decontaminating to Decontaminated)",
                          "Automatically set decontaminating to decontaminated",
                          selectedNeighbourhoodType,
                          cellularAutomata.RadiusVisibility);

            newDecontaminatedRule.CurrentCellStateTypeId = (int)EnumCellStateType.Decontaminating;
            newDecontaminatedRule.ResultCellStateTypeId = (int)EnumCellStateType.Decontaminated;
            newDecontaminatedRule.IsSystemGenerated = true;

            cellularAutomata.TransitionRules.Add(newDecontaminatedRule);

        }
    }

    public static class ObjectCopier
    {

        public static T Clone<T>(this T source)
        {
            T cloned = (T)Activator.CreateInstance(source.GetType());

            foreach (PropertyInfo curPropInfo in source.GetType().GetProperties())
            {
                if (curPropInfo.GetGetMethod() != null && (curPropInfo.GetSetMethod() != null))
                {
                    // Handle Non-indexer properties
                    if (curPropInfo.Name != "Item")
                    {
                        // get property from source
                        object getValue = curPropInfo.GetGetMethod().Invoke(source, new object[] { });

                        // clone if needed
                        if (getValue != null && getValue is DependencyObject)
                            getValue = Clone((DependencyObject)getValue);

                        // set property on cloned
                        curPropInfo.GetSetMethod().Invoke(cloned, new object[] { getValue });
                    }
                    // handle indexer
                    else
                    {
                        // get count for indexer
                        int numberofItemInColleciton = (int)curPropInfo.ReflectedType.GetProperty("Count").GetGetMethod().Invoke(source, new object[] { });

                        // run on indexer
                        for (int i = 0; i < numberofItemInColleciton; i++)
                        {
                            // get item through Indexer
                            object getValue = curPropInfo.GetGetMethod().Invoke(source, new object[] { i });

                            // clone if needed
                            if (getValue != null && getValue is DependencyObject)
                                getValue = Clone((DependencyObject)getValue);
                            // add item to collection
                            curPropInfo.ReflectedType.GetMethod("Add").Invoke(cloned, new object[] { getValue });
                        }
                    }
                }
            }

            return cloned;
        }
    }
}
